#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <dirent.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <getopt.h>
#include <math.h>

#include <gr_graph_fun.h>
#include <gr_structure.h>
#include <gr_xmlwrite.h>
#include <gr_node.h>

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#define MAX_POS_TRIES 100

enum algorithms { ALGO_NONE, ALGO_TREE, ALGO_FORCE_DIRECTED, ALGO_RANDOM };

struct pr_pos *get_next_node_pos(GraphFrame *graph) {
/*
 *	input:	current graph and a location by reference
 *	output:	location updated with the desired location of the next node
 *
 *	note:		The attempt here is to give each node a random position not too close
 *		to other vertices.
 */
	int i=0;
	int x = get_width(graph);
	int y = get_height(graph);
	struct pr_pos *loc = NULL;
	
	if(!(loc = (struct pr_pos *)malloc(sizeof(struct pr_pos)))) {
		perror("malloc()");
		exit(EXIT_FAILURE);
	}

	do {
		loc->x = (int)rand()%x;
		loc->y = (int)rand()%y;
	} while(select_nearest_vertex(loc, graph) != NULL && ++i<MAX_POS_TRIES);
	if(i == MAX_POS_TRIES) {
		fprintf(stderr,"Graph window too full, expanding area\n");
		graph->w = 2*x;
		graph->h = 2*y;
		return get_next_node_pos(graph);
	}
	
	return loc;
}


void print_graph(GraphFrame *graph) {
/*
 *	input:	a graph
 *	output:	that graph printed to stdout in XGMML
 */
	char *graphText = NULL;
	long len = 0;
	if((graphText = get_xml_text_graph(graph,&len,GR_STANDARD_FORMAT))) {
		printf("%s",graphText);
		free(graphText);
	}
	else
		fprintf(stderr,"Cannot print graph.\n");
}

void fs_scan(GraphFrame *graph, PNODE parent, char *directory) {
/*
 *	input:	The graph and the directory to scan
 *	output:	the directory scanned, files added to the graph, any directory recursively scanned
 */
	DIR *cDir = NULL;
	struct dirent *dirEnt = NULL;
	struct stat fStat;
	char fullPath[MAXPATHLEN];
	PNODE curr = NULL;

	if(!(cDir = opendir(directory))) {
		perror(directory);
		return;
	}

	readdir(cDir);	/* . */
	readdir(cDir);	/* .. */

	while((dirEnt = readdir(cDir))) {
		snprintf(fullPath,MAXPATHLEN,"%s/%s",directory,dirEnt->d_name);

		if(lstat(fullPath,&fStat) < 0) {
			perror(fullPath);
			exit(EXIT_FAILURE);
		}

		adding_vertex(graph, fullPath, "1", get_next_node_pos(graph));
		curr = get_last_vertex(graph);
		adding_edge(graph,parent,curr,"1");
		
		switch(fStat.st_mode & S_IFMT) {
			case S_IFREG:
				set_vertex_shape(curr,BOX);
				break;
			case S_IFDIR:
				fs_scan(graph, curr, fullPath);
				set_vertex_shape(curr,CIRCULAR);
				break;
			case S_IFLNK:
				set_vertex_shape(curr,TRIANGLE);
				break;
			case S_IFIFO:
				set_vertex_shape(curr,OCTAGON);
				break;
			case S_IFSOCK:
				set_vertex_shape(curr,RHOMBUS);
				break;
			case S_IFBLK:
				set_vertex_shape(curr,PENTAGON);
				break;
			case S_IFCHR:
				set_vertex_shape(curr,HEXAGON);
				break;
			default:
				set_vertex_shape(curr,OCTAGON);
				fprintf(stderr,"%s is of unknown type\n", fullPath);
		}
	}
	if(closedir(cDir) < 0) {
		perror("closedir()");
		exit(EXIT_FAILURE);
	}
}

int main(int argc, char **argv) {
	char o;
	char directory[MAXPATHLEN];
	char absdir[MAXPATHLEN];
	GraphFrame *graph = NULL;
	PNODE root = NULL;

	short algo = ALGO_RANDOM;

	struct option opt_long[] = {
		{"directory",required_argument,NULL,'d'},
		{"version",0,NULL,'v'},
		{"help",0,NULL,'h'},
		{"force-directed",0,NULL,'f'},
		{"tree",0,NULL,'t'},
		{"random",0,NULL,'r'},
		{0,0,0,0}
	};

	directory[0] = '\0';

	while((o = getopt_long(argc, argv, "d:vhftr",opt_long,NULL)) != EOF) {
		switch(o) {
		case 'd':
			strcpy(directory,optarg);
			break;
		case 'f':
			algo = ALGO_FORCE_DIRECTED;
			break;
		case 't':
			algo = ALGO_TREE;
			break;
		case 'r':
			algo = ALGO_RANDOM;
			break;
		case 'v':
			printf("%s\n",PACKAGE_STRING);
			exit(EXIT_SUCCESS);
		case 'h':
			printf("%s\n",PACKAGE_STRING);
			printf("Usage: %s [--directory=DIRECTORY] [--version] [--help] ",PACKAGE);
			printf("[--force-directed | --tree | --random]\n\n");
			printf("  -d, --directory=DIRECTORY        Scan the given directory\n");
			printf("  -f, --force-directed             Output graph with force-directed positions\n");
			printf("  -t, --tree                       Output graph with nodes positioned as a tree\n");
			printf("  -r, --random                     Output graph with random node positions\n");
			printf("  -v, --version                    Output version information and exit\n");
			printf("  -h, --help                       Display this help and exit\n");
			exit(EXIT_SUCCESS);
		}
	}

	if(!getcwd(absdir,MAXPATHLEN)) {
		perror("getcwd()");
		exit(EXIT_FAILURE);
	}
	if(directory[0]) {
		if(chdir(directory) < 0) {
			perror(directory);
			exit(EXIT_FAILURE);
		}
		if(!getcwd(directory,MAXPATHLEN)) {
			perror("getcwd()");
			exit(EXIT_FAILURE);
		}
		if(chdir(absdir) < 0) {
			perror("chdir()");
			exit(EXIT_FAILURE);
		}
	}
	else {
		strcpy(directory,absdir);
	}
	
	graph = create_current_graph();
	init_basic_graph(graph);
	set_graph_dir(graph,DIRECTED);
	
	adding_vertex(graph,directory,"1",get_next_node_pos(graph));
	root = get_last_vertex(graph);
	set_vertex_shape(root,CIRCULAR);

	fs_scan(graph, root, directory);

	switch(algo) {
	case ALGO_TREE:
		compute_tree_positions(graph,root,NORTH);
		break;
	case ALGO_FORCE_DIRECTED:
		compute_random_positions(graph);
		compute_force_directed_positions(graph);
		break;
	case ALGO_RANDOM:
		break;
	}
	move_graph_to_given_position(graph,0,0);
	
	print_graph(graph);

	return EXIT_SUCCESS;
}
